﻿namespace AppLibrary
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.ServiceModel;
    using System.ServiceModel.Syndication;
    using System.ServiceModel.Web;
    using System.Text;
    using System.Xml;
    using AppLibrary.Storage;
    using AppLibrary.Util;

    public class AtomPubService : IAtomPub
    {
        private const String Atom10Namespace = "http://www.w3.org/2005/Atom";
        private const String PlainTextContentType = "text/plain";
        private const String HtmlContentType = "text/html";
        private const String XHtmlContentType = "application/xhtml+xml";

        private Dictionary<String, ISyndicationProvider> resources;

        public Dictionary<String, ISyndicationProvider> Resources
        {
            get { return this.resources; }
            set { this.resources = value; }
        }

        public AtomPubService()
        {
            this.resources = new Dictionary<String, ISyndicationProvider>();
        }

        public AtomPubService(Dictionary<String, ISyndicationProvider> resources)
        {
            this.resources = new Dictionary<String, ISyndicationProvider>(resources);
        }

        public XmlElement ServiceDocument()
        {
            throw new NotImplementedException();
        }

        public SyndicationFeed GetEntries(String resource)
        {
            ISyndicationProvider provider = this.resources[resource];
            return provider.GetFeed(provider.Items);
        }

        public SyndicationItem CreateEntry(String resource, SyndicationItem entry)
        {
            ISyndicationProvider provider = this.resources[resource];
            return provider.CreateItem(entry);
        }

        public SyndicationItem GetEntry(String resource, String entryId)
        {
            ISyndicationProvider provider = this.resources[resource];
            return provider.GetItem(entryId);
        }

        public SyndicationItem UpdateEntry(String resource, String entryId, SyndicationItem entry)
        {
            ISyndicationProvider provider = this.resources[resource];
            return provider.UpdateItem(entryId, entry);
        }

        public void DeleteEntry(String resource, String entryId)
        {
            ISyndicationProvider provider = this.resources[resource];
            provider.DeleteItem(entryId);
        }

        public SyndicationItem CreateEntryContent(String resource, Stream content)
        {
            ISyndicationProvider provider = this.resources[resource];

            // Get Request Information
            IncomingWebRequestContext requestContext = WebOperationContext.Current.IncomingRequest;
            String contentType = requestContext.ContentType;
            Uri baseUri = OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri;
            Encoding encoding = null;
            if (!String.IsNullOrEmpty(requestContext.Headers[HttpRequestHeader.ContentEncoding]))
            {
                encoding = Encoding.GetEncoding(requestContext.Headers[HttpRequestHeader.ContentEncoding]);
            }

            // Create an empty item
            SyndicationItem item = new SyndicationItem();
            item.Title = new TextSyndicationContent(
                !String.IsNullOrEmpty(requestContext.Headers["Slug"]) ?
                requestContext.Headers["Slug"] :
                "Untitled");
            item.Authors.Add(provider.GetFeed(null).Authors[0]);

            // Check for text content
            if (contentType.Equals(AtomPubService.PlainTextContentType, StringComparison.OrdinalIgnoreCase))
            {
                item.Content = ReadContent(content, encoding, TextSyndicationContentKind.Plaintext);
            }
            else if (contentType.Equals(AtomPubService.HtmlContentType, StringComparison.OrdinalIgnoreCase))
            {
                item.Content = ReadContent(content, encoding, TextSyndicationContentKind.Html);
            }
            else if (contentType.Equals(AtomPubService.XHtmlContentType, StringComparison.OrdinalIgnoreCase))
            {
                item.Content = ReadContent(content, encoding, TextSyndicationContentKind.XHtml);
            }

            // Create the item, this gets the item ID and 
            item = provider.CreateItem(item);

            SyndicationLink editLink = new SyndicationLink(AtomPubUrlProvider.GetItemUri(baseUri, resource, item));
            editLink.RelationshipType = "edit";
            item.Links.Add(editLink);

            // Create binary content resource, update item with links
            if (item.Content == null)
            {
                BinarySyndicationContent newContent =
                    provider.CreateContent(new BinarySyndicationContent(item, contentType, content));

                item.Content = new UrlSyndicationContent(
                    AtomPubUrlProvider.GetContentUri(baseUri, resource, item),
                    contentType);

                SyndicationLink editMediaLink = new SyndicationLink(
                    AtomPubUrlProvider.GetContentUri(baseUri, resource, item),
                    "edit-media",
                    null,
                    contentType,
                    newContent.Data.Length);
                item.Links.Add(editMediaLink);
            }

            return provider.UpdateItem(item.Id, item);
        }

        public Stream GetEntryContent(String resource, String entryId)
        {
            ISyndicationProvider provider = this.resources[resource];

            Uri baseUri = OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri;

            SyndicationItem item = provider.GetItem(entryId);
            Stream data = null;

            if (item.Content is UrlSyndicationContent)
            {
                UrlSyndicationContent urlContent = (UrlSyndicationContent)item.Content;

                String contentResource = null;
                String contentEntryId = null;
                Boolean isMatch = AtomPubUrlProvider.TryParseContentUri(
                    baseUri,
                    urlContent.Url,
                    ref contentResource,
                    ref contentEntryId);

                if (isMatch)
                {
                    ISyndicationProvider contentProvider = provider;
                    if (resource != contentResource)
                    {
                        contentProvider = this.resources[contentResource];
                    }

                    BinarySyndicationContent content = contentProvider.GetContent(contentEntryId);
                    data = new MemoryStream(content.Data);
                }
                else
                {
                    WebRequest request = WebRequest.Create(urlContent.Url);
                    WebResponse response = request.GetResponse();
                    data = response.GetResponseStream();
                    // NOTE: We're trusting WCF to close this stream in a timely fashion
                }
            }
            else if (item.Content is TextSyndicationContent)
            {
                data = new MemoryStream(Encoding.UTF8.GetBytes(((TextSyndicationContent)item.Content).Text));
            }
            else if (item.Content != null)
            {
                MemoryStream buffer = new MemoryStream();
                using (XmlTextWriter writer = new XmlTextWriter(buffer, Encoding.UTF8))
                {
                    item.Content.WriteTo(writer, "content", AtomPubService.Atom10Namespace);
                    writer.Flush();
                }

                buffer.Position = 0;
                using (XmlTextReader reader = new XmlTextReader(new StreamReader(buffer, Encoding.UTF8)))
                {
                    reader.ReadStartElement("content");
                    data = new MemoryStream(Encoding.UTF8.GetBytes(reader.ReadContentAsString()));
                }
            }

            return data;
        }

        public SyndicationItem UpdateEntryContent(String resource, String entryId, Stream content)
        {
            ISyndicationProvider provider = this.resources[resource];
            
            IncomingWebRequestContext requestContext = WebOperationContext.Current.IncomingRequest;
            Uri baseUri = OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri;

            SyndicationItem item = provider.GetItem(entryId);

            UrlSyndicationContent urlContent = item.Content as UrlSyndicationContent;
            String contentResource = null;
            String contentEntryId = null;

            if (urlContent != null &&
                AtomPubUrlProvider.TryParseContentUri(baseUri, urlContent.Url, ref contentResource, ref contentEntryId) &&
                contentResource == resource)
            {
                // Modify the existing binary content
                BinarySyndicationContent existingContent = provider.GetContent(entryId);
                existingContent.Data = content.ReadToEnd();
                provider.UpdateContent(entryId, existingContent);

                item.Content = new UrlSyndicationContent(urlContent.Url, requestContext.ContentType);

                SyndicationLink editMediaLink =
                    (from link in item.Links where link.RelationshipType == "edit-media" select link).First();

                editMediaLink.MediaType = requestContext.ContentType;
                editMediaLink.Length = existingContent.Data.Length;
            }
            else
            {
                BinarySyndicationContent newContent =
                    new BinarySyndicationContent(item, requestContext.ContentType, content.ReadToEnd());
                newContent = provider.CreateContent(newContent);

                item.Content = new UrlSyndicationContent(
                    AtomPubUrlProvider.GetContentUri(baseUri, resource, item),
                    requestContext.ContentType);

                SyndicationLink editMediaLink =
                    (from link in item.Links where link.RelationshipType == "edit-media" select link).FirstOrDefault();

                if (editMediaLink != null)
                {
                    editMediaLink.Uri = AtomPubUrlProvider.GetContentUri(baseUri, resource, item);
                    editMediaLink.MediaType = requestContext.ContentType;
                    editMediaLink.Length = newContent.Data.Length;
                }
                else
                {
                    editMediaLink = new SyndicationLink(
                        AtomPubUrlProvider.GetContentUri(baseUri, resource, item),
                        "edit-media",
                        null,
                        requestContext.ContentType,
                        newContent.Data.Length);

                    item.Links.Add(editMediaLink);
                }
            }
            
            return provider.UpdateItem(item.Id, item);
        }

        public void DeleteEntryContent(String resource, String entryId)
        {
            ISyndicationProvider provider = this.resources[resource];

            SyndicationItem item = provider.GetItem(entryId);
            if (item.Content != null)
            {
                item.Content = null;

                SyndicationLink editMediaLink =
                    (from link in item.Links where link.RelationshipType == "edit-media" select link).FirstOrDefault();

                if (editMediaLink != null)
                {
                    item.Links.Remove(editMediaLink);
                }

                provider.UpdateItem(item.Id, item);
            }

            provider.DeleteContent(entryId);
        }

        #region helper functions

        private static TextSyndicationContent ReadContent(Stream content,
                                                          Encoding encoding,
                                                          TextSyndicationContentKind contentKind)
        {
            if (encoding == null)
            {
                using (StreamReader reader = new StreamReader(content, true))
                {
                    return new TextSyndicationContent(reader.ReadToEnd(), contentKind);
                }
            }
            else
            {
                using (StreamReader reader = new StreamReader(content, encoding))
                {
                    return new TextSyndicationContent(reader.ReadToEnd(), contentKind);
                }
            }
        }

        #endregion
    }
}
